use std::any::{Any};
use std::fmt;
use tree;
use signal;

use super::core::{
  GUI, GUIContext, ComponentAny,
  dynamic,
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Component trait

/// Trait for user-definable GUI components.
pub trait Component: Sized + PartialEq + 'static {
  type State: PartialEq;

  /// Generate an initial state for this component.
  fn init(&self) -> Self::State;

  /// Render this component when it has the given state.
  fn render(&self, ctx: Ctx<Self>) -> GUI;

  /// Return `true` if the next configuration/state indicates that the component should be updated
  /// and rerendered.
  fn should_update(&self, s: &Self::State, next: &Self, next_s: &Self::State) -> bool {
    *self != *next || *s != *next_s
  }

  /// Create a GUI element for this component.
  fn into_gui(self) -> GUI { dynamic(self) }
}

// Pure component trait

pub trait PureComponent: Sized + Eq + 'static {
  /// Render this pure component.
  fn render(&self) -> GUI;
  /// Create a GUI element for this pure component.
  fn into_gui(self) -> GUI { dynamic(PureComp(self)) }
}

#[derive(PartialEq)]
struct PureComp<T>(T);

impl<T> Component for PureComp<T> where T: PureComponent {
  type State = ();
  fn init(&self) {}
  fn render(&self, _: Ctx<Self>) -> GUI { self.0.render() }
  fn should_update(&self, _: &(), next: &Self, _: &()) -> bool { self.0 != next.0 }
}

// Auto-promote Component to ComponentAny by erasing & recovering type info.

impl<T> ComponentAny for T where T: Component {
  fn as_any(&self) -> &dyn Any {
    self
  }

  fn init(&self) -> Box<dyn Any> {
    Box::new(T::init(self))
  }

  fn render(&self, ctx: &GUIContext, s_erased: &dyn Any) -> GUI {
    let state = s_erased.downcast_ref::<T::State>()
      .expect("current state to render() should have correct type");
    T::render(self, Ctx::from_internal(ctx, state))
  }

  fn compatible(&self, cfg: &dyn Any) -> bool {
    cfg.is::<T>()
  }

  fn eq(&self, oth_erased: &dyn Any) -> bool {
    oth_erased
      .downcast_ref::<T>()
      .map(|oth| *oth == *self)
      .unwrap_or(false)
  }

  fn should_update(&self,
                   s_erased: &dyn Any,
                   next_erased: &dyn Any,
                   next_s_erased: &dyn Any) -> bool {
    let s = s_erased.downcast_ref::<T::State>()
      .expect("prev state to should_update() should have correct type");
    match (next_erased.downcast_ref::<T>(),
           next_s_erased.downcast_ref::<T::State>()) {
      (Some(next), Some(next_s)) =>
        T::should_update(self, s, next, next_s),

      (_, _) => {
        println!("warning: dynamic typechecking failed in should_update()");
        false
      }
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Context

/// User-facing context. Contains the component's state, and (will) give hooks for creating event
/// callbacks and modifying the state.
pub struct Ctx<'a, C: Component> {
  path: &'a tree::Path,
  state: &'a C::State,
  evt_disp: signal::EvtDispatcherPtr,
}

impl<'a, C: Component> fmt::Debug for Ctx<'a, C>
where C::State: fmt::Debug {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "Ctx {{ path: {:?}, state: {:?} }}", self.path, self.state)
  }
}

impl<'a, C: Component> Ctx<'a, C> {

  fn from_internal(ctx: &'a GUIContext, state: &'a C::State) -> Self {
    let GUIContext { ref path, ref evt_disp, .. } = *ctx;
    let evt_disp = evt_disp.clone();
    Ctx { state, path, evt_disp }
  }


  // State

  pub fn state(&self) -> &C::State {
    self.state
  }

  pub fn set_state(&self, _s: C::State) {
    unimplemented!()
  }


  // Events & callbacks

  pub fn bind_callback<E, A>(&self, evt: E, cbk: signal::Callback<A>)
  where E: signal::EvtOf<A> {
    evt.bind_to(&mut self.evt_disp.borrow_mut(), cbk)
  }

  pub fn callback<F, A>(&self, func: F) -> signal::Callback<A>
  where F: Fn(Ctx<C>, A) + 'static {
    let evt_disp = self.evt_disp.clone();
    signal::Callback::new(
      self.path.into(),
      move |path, state_erased, arg| {
        let evt_disp = evt_disp.clone();
        let state: &C::State = state_erased.downcast_ref()
          .expect("state to callback function should have correct type");
        func(Ctx { path, state, evt_disp }, arg)
      },
    )
  }

  pub fn bind<E, A, F>(&self, evt: E, func: F)
  where E: signal::EvtOf<A>, F: Fn(Ctx<C>, A) + 'static {
    self.bind_callback(evt, self.callback(func))
  }
}

impl<'a, C: Component> Clone for Ctx<'a, C> {
  fn clone(&self) -> Self {
    let Ctx { path, state, ref evt_disp } = *self;
    let evt_disp = evt_disp.clone();
    Ctx { path, state, evt_disp }
  }
}
